﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Blackbird.Common.Exceptions;

namespace Blackbird.Common
{
    public class Map<T> : DenseGraph<T> where T : IMapNode
    {
        public Map()
        {
        }

        public Map(int width, int height)
            : base(width, height)
        {
        }

        public Collection<IMapNode> PickPath(IMapNode start, IMapNode finish)
        {
            var openNodes = new Collection<IMapNode>();
            var closedNodes = new Collection<IMapNode>();

            openNodes.Add(start);
            var gScores = new [] {14, 10, 14, 10, 14, 10, 14, 10};
            var currentSet = new IMapNode[8];

            IMapNode currentNode = start;
            while (currentNode != finish)
            {
                currentSet[0] = this[currentNode.X - 1, currentNode.Y - 1];
                currentSet[1] = this[currentNode.X, currentNode.Y - 1];
                currentSet[2] = this[currentNode.X + 1, currentNode.Y - 1];
                currentSet[3] = this[currentNode.X + 1, currentNode.Y];
                currentSet[4] = this[currentNode.X + 1, currentNode.Y + 1];
                currentSet[5] = this[currentNode.X, currentNode.Y + 1];
                currentSet[6] = this[currentNode.X - 1, currentNode.Y + 1];
                currentSet[7] = this[currentNode.X - 1, currentNode.Y];

                float lowestF = float.MaxValue;
                int selectedIndex = -1;
                for (int i = 0; i < 8; i++)
                {
                    if (currentSet[i].Impedence > 0)
                    {
                        float f = (float) MeasureManhattanDistance(currentSet[i], finish) + gScores[i];
                        f *= currentSet[i].Impedence;
                        if (currentSet[i] == finish)
                            f = 0;
                        if ((f < lowestF) && (!closedNodes.Contains(currentSet[i])))
                        {
                            lowestF = f;
                            selectedIndex = i;
                        }
                    }
                }
                if (selectedIndex != -1)
                    closedNodes.Add(currentSet[selectedIndex]);
                else
                    throw new NoPathExistsException();
                currentNode = currentSet[selectedIndex];
            }
            return closedNodes;
        }

        private int MeasureManhattanDistance(IMapNode start, IMapNode finish)
        {
            int x = Math.Abs(start.X - finish.X);
            int y = Math.Abs(start.Y - finish.Y);
            return x + y;
        }

        protected void ValidateNode(IMapNode node)
        {
            if ((node.X > GetDimensionExtent(0) - 1)
                || (node.X < 0)
                || (node.Y > GetDimensionExtent(1))
                || (node.Y < 0))
            {
                throw new NodeCoordinatesOutOfBoundsException();
            }
        }

        public List<T> UnwrapGraph()
        {
            return StorageArray.ToList();
        }
    }
}